package ga.chromosome;
import ga.fitness.FitnessCalc;
import ga.gene.Gene;
import ga.gene.Input;
import ga.gene.TheException;

import java.util.ArrayList;


/**
 * The Class Chromosome.
 */
public class Chromosome {
	
	ArrayList<Gene> geneArray = new ArrayList<Gene>();
	ArrayList<Input> inputArray = new ArrayList<Input>();
	ArrayList<ArrayList<Boolean>> outputs;
	Input properOutput = null;
	Integer match  = null;
	public Chromosome answer;
	
	int fitness;
	
	private static int defaultInputs;
	private static int rows = 2;
	private static int layers = 2;

	/**
	 * Instantiates a new chromosome.
	 */
	public Chromosome(){
		geneArray = new ArrayList<Gene>(rows * layers);
		inputArray = new ArrayList<Input>(Chromosome.defaultInputs);
	}
	public Chromosome(int fitness){
		geneArray = new ArrayList<Gene>(fitness);
		inputArray = new ArrayList<Input>(fitness*2);
	}
	/**
	 * Instantiates a new chromosome that is a duplicate of the input chromosome.
	 *
	 * @param chromo the chromo
	 */
	public Chromosome(Chromosome chromo){
		geneArray.add(null);
		inputArray.add(null);
		for(Gene gene : chromo.geneArray)
			if(gene != null)
				geneArray.add(new Gene(gene));
		for(Input input : chromo.inputArray)
			if(input != null)
				inputArray.add(new Input(input));
		
	}
	
	/**
	 * Instantiates a new chromosome using only the inputs given.  These are given
	 * and then it generates all the new inputs it needs.
	 *
	 * @param inputs the inputs
	 * @throws TheException the the exception
	 */
	public Chromosome(ArrayList<Input> inputs) throws TheException{
		Chromosome.defaultInputs = inputs.size()-1;
		geneArray.add(null);
		inputArray = new ArrayList<Input>(inputs);
		for(int i = 0; i < layers; i++){

			for(int j = 0; j < rows; j++)
				geneArray.add(new Gene(inputArray));
		
			for(Gene g : geneArray){
				if(g != null){
					if(g.getOutput() == null){
						Input temp = new Input(geneArray.indexOf(g)+defaultInputs);
						g.setOutput(temp);
						inputArray.add(temp.getNum(), temp);

					}
				}
			}
		}
	}
	
	/**
	 * Solve returns a 2d Boolean ArrayList of every output gates out for every
	 * input.
	 *
	 * @return the array list
	 * @throws TheException the the exception
	 */
	public ArrayList<ArrayList<Boolean>> solve() throws TheException{
		if(outputs == null){
			ArrayList<ArrayList<Boolean>> solution = new ArrayList<ArrayList<Boolean>>();
			boolean[][] inputs = convertTT();
			for(boolean[] array : inputs){
				for(int i = 0; i < array.length; i++){
					try{
					inputArray.get(i+1).setBool(array[i]);
					}
					catch(IndexOutOfBoundsException e){
						System.out.println(e.getMessage());
					}
				}
				solution.add(solveOne());
			}
			outputs = solution;
			return solution;
		}
		return outputs;

	}
	
	/**
	 * Solve one generates all the outputs for every gene in a chromosome.
	 *
	 * @return the array list
	 * @throws TheException the the exception
	 */
	public ArrayList<Boolean> solveOne() throws TheException{
		ArrayList<Boolean> output = new ArrayList<Boolean>();
		for (Gene g : geneArray){
			if(g != null){
				g.geneOutput();
			}
		}
		int startingGene = rows * layers + defaultInputs - (rows-1);
		for(int i = startingGene; i <= rows * layers + defaultInputs; i++){
			try {
				output.add(inputArray.get(i).getBool());
			} catch (Exception e) {
				System.out.println("Break here");
			}
		}
		return output;
	}
	public ArrayList<ArrayList<Boolean>> solveSwitch() throws TheException{
		ArrayList<ArrayList<Boolean>> temp = this.solve();
		ArrayList<ArrayList<Boolean>> returnValue = new ArrayList<ArrayList<Boolean>>();
		for(int j = 0; j < temp.get(0).size(); j++){
			ArrayList<Boolean> temp2 = new ArrayList<Boolean>();
		
			for(int i = 0; i < temp.size(); i++){
				
				temp2.add(temp.get(i).get(j));
			}
			returnValue.add(temp2);
		}
		outputs = returnValue;
		return returnValue;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString(){
		String out = "";
		for(Gene gene : geneArray)
			if(gene != null)
				out += gene + "\n";
		return out;
	}
	
	/**
	 * To bool is very similar to to toString but returns boolean in the place
	 * of the input and output numbers.
	 *
	 * @return the string
	 */
	public String toBool(){
		String out = "";
		for(Gene gene : geneArray)
			if(gene != null)
				out += gene.toBool() + "\n";
		return out;
	}
	
	/**
	 * Gets the fitness for a chromosome.
	 *
	 * @return the fitness
	 * @throws TheException the the exception
	 */
	public int getFitness() throws TheException {
        if (fitness == 0) {
        	fitness = FitnessCalc.getFitness(this);
        }
        return fitness;
    }
	
	/**
	 * Gets the layers.
	 *
	 * @return the layers
	 */
	public static int getLayers() {
		return layers;
	}
	
	/**
	 * Sets the layers.
	 *
	 * @param layers the new layers
	 */
	public static void setLayers(int layers) {
		Chromosome.layers = layers;
	}
	
	/**
	 * Gets the rows.
	 *
	 * @return the rows
	 */
	public static int getRows() {
		return rows;
	}
	
	/**
	 * Sets the rows.
	 *
	 * @param rows the new rows
	 */
	public static void setRows(int rows) {
		Chromosome.rows = rows;
	}
	
	/**
	 * Gets the default inputs.
	 *
	 * @return the default inputs
	 */
	public static int getDefaultInputs() {
		return defaultInputs;
	}
	
	/**
	 * Gets the default genes.
	 *
	 * @return the default genes
	 */
	public static int getDefaultGenes() {
		return rows * layers;
	}
	
	/**
	 * Sets the match which is the output gate with the correct output.
	 *
	 * @param input the new match
	 */
	public void setMatch(int input){
		match = input;
		setCorrectOutput(inputArray.get(rows * layers + defaultInputs - rows + 1 + match));
	}
	
	/**
	 * Gets the gene.
	 *
	 * @param index the index
	 * @return the gene
	 */
	public Gene getGene(int index){
		return geneArray.get(index);
	}
	
	/**
	 * Sets the gene.
	 *
	 * @param index the index
	 * @param input the input
	 */
	public void setGene(int index, Gene input){
		try{
			geneArray.set(index,input);
		}
		catch(IndexOutOfBoundsException e){
			System.out.println(e.getMessage());
		}
	}
	
	
	/**
	 * Gets the input.
	 *
	 * @param index the index
	 * @return the input
	 */
	public Input getInput(int index) {
		return inputArray.get(index);
	}

	/**
	 * Sets the input.
	 *
	 * @param index the index
	 * @param input the input
	 */
	public void setInput(int index, Input input) {
		inputArray.set(index,input);
	}

	/**
	 * Gets the match.
	 *
	 * @return the match
	 */
	public Integer getMatch(){
		return match;
	}
	
	/**
	 * Gets the Truth Table.  Generates a 2d int array of 1's and 0's for input
	 * truths.
	 *
	 * @return the tt
	 */
	public int[][] getTT(){
		int intInput = Chromosome.defaultInputs;
		int rowsArray = (int)Math.pow(2, intInput);
		int[][] output = new int[rowsArray][intInput];
		for(int i = 0; i<rowsArray; i++){
			for(int j = intInput-1; j>=0; j--){
				output[i][intInput-1-j] = (i / ((int) Math.pow(2, j))) % 2;
			}
		}

		return output;
	}
	
	/**
	 * Converts the above Truth Table to a 2d boolean value of true's and false's
	 * instead of 1's and 0's.
	 *
	 * @return the boolean[][]
	 */
	public boolean[][] convertTT(){
		int[][] input = getTT();
		boolean[][] output = new boolean[input.length][input[0].length];
		for(int i = 0; i < input.length; i++){
			for(int j = 0; j < input[i].length; j++){
				if(input[i][j] == 1)
					output[i][j] = true;
				else
					output[i][j] = false;					
				
			}
		}
		return output;
	}

	/**
	 * Mutate will take a certain gene and mutate it, then solves again.
	 *
	 * @param i the i
	 * @throws TheException the the exception
	 */
	public void mutate(int i) throws TheException {
		Gene temp = geneArray.get(i);
		geneArray.set(i,temp.mutate());
		outputs = null;
		match  = null;
		solve();		
	}
	
	/**
	 * Trace output is used to find the fitness, will simply find the correct output.
	 * 
	 *
	 * @return the int
	 */
	public int traceOutput(){
		if(match != null){
			Gene output = findOutput(inputArray.get(rows * layers + defaultInputs - rows + 1 + match));
			answer = new Chromosome(10);
			return traceOutput(output.getOutput());
		}
		return 100;
	}
	
	/**
	 * Finds the output and will recursively get the weight (1 for each gate)
	 *
	 * @param in the in
	 * @return the int
	 */
	public int traceOutput(Input in){
		if(in.getNum() <= defaultInputs)
			return 1;
		
		Gene gene = findOutput(in);
		answer.addGene(gene);
		Input in1 = gene.getInput1();
		Input in2 = gene.getInput2();
		
		if(in1.getNum() <= defaultInputs && in2.getNum() <= defaultInputs)
			return 1;
		else if(in1.getNum() < defaultInputs)
			return 1 + traceOutput(in2);
		else if(in1.getNum() < defaultInputs)
			return 1 + traceOutput(in1);
		else
			return 1 + traceOutput(in1) + traceOutput(in2);
	}
	
	/**
	 * Adds the gene.
	 *
	 * @param input the input
	 */
	public void addGene(Gene input){
		geneArray.add(input);
	}
	
	/**
	 * Adds the input.
	 *
	 * @param input the input
	 */
	public void addInput(Input input){
		inputArray.add(input);
	}
	
	/**
	 * Takes and input and finds the gene that that output is associated with
	 *
	 * @param output the output
	 * @return the gene
	 */
	public Gene findOutput(Input output){
		for(Gene gene : geneArray){
			if(gene != null && gene.getOutput().getNum() == output.getNum()){
				return gene;
			}
		}
		return null;
	}
	
	/**
	 * Gets the boolean output of the correct output gate
	 *
	 * @return the outputs
	 * @throws TheException the the exception
	 */
	public ArrayList<Boolean> getOutputs() throws TheException{
		ArrayList<Boolean> booleanOutput = new ArrayList<Boolean>();
		if(match == null)
			throw new TheException("No matches");
		else if (outputs == null)
			throw new TheException("No outputs");
		else{
			for(int i = 0; i < outputs.size(); i++){
					booleanOutput.add(outputs.get(i).get(match));
				}
			
			return booleanOutput;
		}
	}
	
	/**
	 * Gets the correct outputs number.
	 *
	 * @return the correct output
	 * @throws TheException the the exception
	 */
	public int getCorrectOutput() throws TheException{
		if(properOutput == null)
			throw new TheException("No proper output");
		return properOutput.getNum();
	}
	
	/**
	 * Sets the correct output.
	 *
	 * @param in the new correct output
	 */
	public void setCorrectOutput(Input in){
		properOutput = in;
	}
	
}
